package org.hepeng.workx.spring.boot.autoconfigure.jdbc;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.hepeng.workx.exception.ApplicationRuntimeException;
import org.hepeng.workx.jdbc.DataSourceInfo;
import org.hepeng.workx.jdbc.SelectableDataSource;
import org.hepeng.workx.util.ClassUtils;
import org.joor.Reflect;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.hepeng.workx.spring.boot.autoconfigure.jdbc.MultipleDataSourceProperties.WORKX_MULTIPLE_DATA_SOURCE_PREFIX;

/**
 * @author he peng
 */

@EnableConfigurationProperties(MultipleDataSourceProperties.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class MultipleDataSourceAutoConfiguration {


    @Bean
    @ConditionalOnProperty(prefix = WORKX_MULTIPLE_DATA_SOURCE_PREFIX , name = "enable-multiple-data-source" , havingValue = "true")
    public DataSource selectableDataSource(MultipleDataSourceProperties properties) {
        if (Objects.isNull(properties) || MapUtils.isEmpty(properties.getDatasource())) {
            throw new IllegalStateException("no multiple data source configured");
        }

        SelectableDataSource selectableDataSource = new SelectableDataSource();
        List<DataSourceInfo> dataSourceInfoList = new ArrayList<>();
        for (Map.Entry<String, DataSourceOpsModeProperties> entry : properties.getDatasource().entrySet()) {
            String datasourceId = entry.getKey();
            DataSourceOpsModeProperties dataSourceProperties = entry.getValue();

            DataSource dataSource = null;
            if (conditionalOnClass("org.apache.tomcat.jdbc.pool.DataSource")) {
                dataSource = tomcat(dataSourceProperties);
            }

            if (conditionalOnClass("com.zaxxer.hikari.HikariDataSource")) {
                dataSource = hikari(dataSourceProperties);
            }

            if (conditionalOnClass("org.apache.commons.dbcp2.BasicDataSource")) {
                dataSource = dbcp2(dataSourceProperties);
            }

            if (Objects.nonNull(dataSource)) {
                DataSourceInfo dataSourceInfo = new DataSourceInfo();
                dataSourceInfo.setId(datasourceId);
                dataSourceInfo.setDataSource(dataSource);
                dataSourceInfo.setMode(dataSourceProperties.getMode());
                dataSourceInfoList.add(dataSourceInfo);
            }
        }

        if (CollectionUtils.isEmpty(dataSourceInfoList)) {
            throw new ApplicationRuntimeException("Provide at least one data source available");
        }
        selectableDataSource.addDataSource(dataSourceInfoList);
        return selectableDataSource;
    }

    private boolean conditionalOnClass(String className) {
        return ClassUtils.hasClass(className);
    }

    private DataSource dbcp2(DataSourceOpsModeProperties dataSourceProperties) {
        Reflect tomcatReflect = Reflect.on("org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Dbcp2").create();
        DataSource dataSource = tomcatReflect.call("dataSource", dataSourceProperties).get();
        return dataSource;
    }

    private DataSource hikari(DataSourceOpsModeProperties dataSourceProperties) {
        Reflect tomcatReflect = Reflect.on("org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari").create();
        DataSource dataSource = tomcatReflect.call("dataSource", dataSourceProperties).get();
        return dataSource;
    }

    private DataSource tomcat(DataSourceOpsModeProperties dataSourceProperties) {
        Reflect tomcatReflect = Reflect.on("org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat").create();
        DataSource dataSource = tomcatReflect.call("dataSource", dataSourceProperties).get();
        return dataSource;
    }

}
